//+------------------------------------------------------------------+
//|                                                 TimeManager.mqh  |
//|                                    Copyright 2024, Precision EA  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Precision EA"
#property strict

#include "Logger.mqh"

class TimeManager {
private:
    struct DSTRule {
        int year;
        datetime spring_forward; // DST start
        datetime fall_back;      // DST end
    };
    
    DSTRule m_dst_rules[20];
    int     m_rules_count;
    string  m_timezone;
    
    bool LoadDSTRules() {
        // Règles DST pour l'Europe/Paris 2020-2040
        DSTRule rules[] = {
            {2024, D'2024.03.31 02:00', D'2024.10.27 03:00'},
            {2025, D'2025.03.30 02:00', D'2025.10.26 03:00'},
            {2026, D'2026.03.29 02:00', D'2026.10.25 03:00'},
            {2027, D'2027.03.28 02:00', D'2027.10.31 03:00'},
            {2028, D'2028.03.26 02:00', D'2028.10.29 03:00'},
            {2029, D'2029.03.25 02:00', D'2029.10.28 03:00'},
            {2030, D'2030.03.31 02:00', D'2030.10.27 03:00'}
        };
        
        ArrayCopy(m_dst_rules, rules);
        m_rules_count = ArraySize(rules);
        
        Logger::Info(StringFormat("Loaded %d DST rules", m_rules_count), "TimeManager");
        return m_rules_count > 0;
    }
    
    int FindYearIndex(datetime dt) {
        MqlDateTime mql_dt;
        TimeToStruct(dt, mql_dt);
        
        for(int i = 0; i < m_rules_count; i++) {
            if(m_dst_rules[i].year == mql_dt.year) {
                return i;
            }
        }
        return -1;
    }

public:
    TimeManager(string timezone = "Europe/Paris") : m_timezone(timezone) {
        if(!LoadDSTRules()) {
            Logger::Critical("Failed to load DST rules", "TimeManager");
        }
    }
    
    bool IsDST(datetime utc_time) {
        int year_idx = FindYearIndex(utc_time);
        if(year_idx == -1) {
            Logger::Warn(StringFormat("No DST rule for year %d", TimeYear(utc_time)), "TimeManager");
            return false;
        }
        
        return (utc_time >= m_dst_rules[year_idx].spring_forward && 
                utc_time < m_dst_rules[year_idx].fall_back);
    }
    
    datetime UTCToLocal(datetime utc_time) {
        if(m_timezone == "Europe/Paris") {
            return utc_time + (IsDST(utc_time) ? 7200 : 3600);
        }
        else if(m_timezone == "America/New_York") {
            return utc_time + (IsDST(utc_time) ? 14400 : 18000); // UTC-4/-5
        }
        else if(m_timezone == "UTC") {
            return utc_time;
        }
        
        // Fallback to broker time
        return utc_time + TimeGMTOffset();
    }
    
    datetime LocalToUTC(datetime local_time) {
        if(m_timezone == "Europe/Paris") {
            // Approximation itérative
            datetime guess_utc = local_time - 3600; // Start with winter offset
            for(int i = 0; i < 5; i++) {
                datetime calculated_local = UTCToLocal(guess_utc);
                if(MathAbs(calculated_local - local_time) <= 1) {
                    return guess_utc;
                }
                guess_utc += (local_time - calculated_local);
            }
        }
        return local_time - TimeGMTOffset();
    }
    
    bool IsLondonSession(datetime utc_time) {
        datetime local = UTCToLocal(utc_time);
        MqlDateTime dt;
        TimeToStruct(local, dt);
        
        // Monday to Friday
        if(dt.day_of_week == 0 || dt.day_of_week == 6) {
            return false;
        }
        
        // 8:00-17:00 Paris time (7:00-16:00 UTC in winter, 6:00-15:00 UTC in summer)
        int hour_paris = dt.hour;
        return (hour_paris >= 8 && hour_paris < 17);
    }
    
    bool IsTradingHours(datetime utc_time, string session = "London+NY") {
        if(!IsLondonSession(utc_time)) {
            return false;
        }
        
        if(session == "London+NY") {
            datetime local = UTCToLocal(utc_time);
            MqlDateTime dt;
            TimeToStruct(local, dt);
            
            // NY close at 22:30 Paris time
            if(dt.hour > 22 || (dt.hour == 22 && dt.min >= 30)) {
                return false;
            }
        }
        
        return true;
    }
    
    datetime GetNextTradingTime(datetime from_time = 0) {
        if(from_time == 0) from_time = TimeCurrent();
        
        // Advance in 1-hour increments until we find trading time
        datetime check_time = from_time;
        for(int i = 0; i < 168; i++) { // Max 1 week ahead
            if(IsTradingHours(check_time)) {
                return check_time;
            }
            check_time += 3600; // Add 1 hour
        }
        
        return 0; // Should not happen
    }
    
    string GetCurrentTimeInfo() {
        datetime utc_now = TimeCurrent();
        datetime local_now = UTCToLocal(utc_now);
        
        return StringFormat("UTC: %s | %s: %s | DST: %s | Trading: %s",
            TimeToString(utc_now, TIME_DATE|TIME_SECONDS),
            m_timezone,
            TimeToString(local_now, TIME_DATE|TIME_SECONDS),
            IsDST(utc_now) ? "Yes" : "No",
            IsTradingHours(utc_now) ? "Yes" : "No"
        );
    }
};